﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace MeTool.BarCode
{
    /// <summary>
    /// 此类用于操作58类型的POS打印机,每行打印32个字符
    /// </summary>
    /// <remarks>图像打印时,8点密度下一个字节表示一列数据,24点密度下连续的三个字节表示一列数据. 每列数据在行方向上只占用一点. 
    /// 打印机设置方法:在系统中任意建立一个打印机,指定正确的端口,然后为doPrint或者doPrintM函数的theProtName参数指定这个打印机名称即可. 
    /// 网络打印机设置方法:目标机器按正常方法在系统中设置好打印机,然后共享并指定一个不带空格的共享名称,
    /// 最后为doPrint或者doPrintM函数的theProtName参数按\\计算机名称或者IP地址\打印机名称的格式指定这个打印机</remarks>
    public static class POS
    {
        /// <summary>
        /// 返回/设置单据尾部的走纸行数
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        /// <remarks></remarks>
        public static int MargenLines
        {
            get
            {
                if (mMargenLines < 0) return 0;
                if (mMargenLines > 20) return 20;
                return mMargenLines;
            }
            set
            {
                if (value < 0)
                    mMargenLines = 0;
                else if (value > 20)
                    mMargenLines = 20;
                else
                    mMargenLines = value;
            }
        }

        /// <summary>
        /// 获取标头的POS码,起始字符：!=倍高，^=倍宽，#=倍高+倍宽. 左边可以附加空格,已换行
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string GetTitle(string text)
        {
            var dSpecLen = (text.Length - text.TrimStart(new char[0]).Length);
            text = text.Trim();
            var dInt = 0;
            if (text.StartsWith("!"))
            {
                text = text.Substring(1);
                dInt = 16;
            }
            else if (text.StartsWith("^"))
            {
                text = text.Substring(1);
                dInt = 32;
            }
            else if (text.StartsWith("#"))
            {
                text = text.Substring(1);
                dInt = 48;
            }
            return new string(' ', dSpecLen) + "\u001b!" + ((char)dInt).ToString() + text + "\u001b!\0\r\n";
        }

        /// <summary>
        /// 切纸0
        /// </summary>
        /// <returns></returns>
        public static string Knife0()
        {
            return "\u001bi\0";
        }

        /// <summary>
        /// 切纸1
        /// </summary>
        /// <returns></returns>
        public static string Knife1()
        {
            return "\u001bi\u0001";
        }

        /// <summary>
        /// 获取被设置为倍宽的文本
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string GetText2Width(string text)
        {
            return "\u001b! " + text + "\u001b!\0";
        }

        /// <summary>
        /// 获取被设置为倍高的文本
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string GetText2Height(string text)
        {
            return "\u001b!\u0010" + text + "\u001b!\0";
        }

        /// <summary>
        /// 获取大字型的文本(倍宽+倍高)
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static string GetTextBig(string text)
        {
            return "\u001b!0" + text + "\u001b!\0";
        }

        /// <summary>
        /// 初始化打印机
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string PrintNew()
        {
            return "\u001b@";
        }

        /// <summary>
        /// 钱箱控制(开钱箱)
        /// </summary>
        /// <param name="type">钱箱类型,0~2</param>
        /// <param name="position">引脚类型,0~3</param>
        /// <param name="polarity">电压极性,0~3</param>
        /// <returns></returns>
        /// <remarks>大部分小票打印机的钱箱设置方法:钱箱类型大都为0,引脚类型一般是0或者1,电压极性0~3都有可能</remarks>
        public static string MoneyBoxOpen(int type, int position, int polarity)
        {
            string dText;
            if (type > 1)
            {
                if (type != 2) return "";

                dText = "\u0010";
                dText += "\u0014";
                dText += "\u0001";
                if (position == 0)
                    dText += "\0";
                else
                    dText += "\u0001";
                if (polarity == 0)
                    dText += "\u0002";
                else
                    dText += "\u0004";
            }
            else
            {
                dText = "\u001b";
                if (type == 0)
                    dText += "p";
                else
                    dText += "9";
                switch (position)
                {
                    case 0:
                        dText += "\0";
                        break;
                    case 1:
                        dText += "\u0001";
                        break;
                    case 2:
                        dText += "0";
                        break;
                    case 3:
                        dText += "1";
                        break;
                    default:
                        return "";
                }
                switch (polarity)
                {
                    case 0:
                        dText += "01";
                        break;
                    case 1:
                        dText += "10";
                        break;
                    case 2:
                        dText += "\0\u001e";
                        break;
                    case 3:
                        dText += "\u001e\0";
                        break;
                    default:
                        return "";
                }
            }
            return dText;
        }

        /// <summary>
        /// 打印并走纸n点行
        /// </summary>
        /// <param name="theRowCount"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string GetPrintMargin(int rowCount)
        {
            string dText = "";
            for (int xx = 0; xx < rowCount; xx++) dText += "\r\n";
            return dText;
        }

        /// <summary>
        /// 设置字符行间距
        /// </summary>
        /// <param name="size"></param>
        /// <param name="text"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string GetRowSize(byte size, string text)
        {
            return "\u001b3" + ((char)(int)size).ToString() + text + "\u001b2";
        }

        /// <summary>
        /// 返回一行点线条,已经包含了回车换行符
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string GetLineDot()
        {
            var dText = "";
            var xx = 0;
            do
            {
                dText += "…";
                xx++;
            }
            while (xx <= 15);
            return dText + "\r\n";
        }

        /// <summary>
        /// 返回给定文本的字节长度
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static int GetLenB(string text)
        {
            return Encoding.Default.GetByteCount(text);
        }

        /// <summary>
        /// 整理文本,把任意长度的文本裁切/补足成固定字节长度(长度按单字节=1,双字节=2计算)
        /// </summary>
        /// <param name="text">要处理的文本</param>
        /// <param name="lengthB">要处理到的字节长度</param>
        /// <param name="manyIsDot">当长度超出时是否显示省略号</param>
        /// <param name="alignRight">是按照左对齐方式裁切(切掉右边)还是按照右对齐方式裁切(切掉左边)</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string DealText(string text, int lengthB, bool manyIsDot = false, bool alignRight = false)
        {
            var dGBKLength = GetLenB(text);
            if (lengthB <= 0)
            {
                return "";
            }
            else if (lengthB == 1)
            {
                return dGBKLength == 1 ? text : " ";
            }
            else if (lengthB == 2)
            {
                switch (dGBKLength)
                {
                    case 0:
                        return "  ";
                    case 1:
                        if (alignRight)
                        {
                            return " " + text;
                        }
                        else
                        {
                            return text + " ";
                        }
                    case 2:
                        return text;
                    default:
                        return manyIsDot ? "…" : "  ";
                }
            }
            else if (dGBKLength <= lengthB)
            {
                if (alignRight)
                {
                    return new string(' ', lengthB - dGBKLength) + text;
                }
                else
                {
                    return text + new string(' ', lengthB - dGBKLength);
                }
            }
            else
            {
                string dDot = "";
                if (manyIsDot)
                {
                    lengthB -= 2;
                    dDot = "…";
                }
                if (text.Length > lengthB)
                {
                    if (alignRight)
                    {
                        text = text.Substring(text.Length - lengthB);
                    }
                    else
                    {
                        text = text.Substring(0, lengthB);
                    }
                }
                while (GetLenB(text) > lengthB)
                {
                    if (alignRight)
                    {
                        text = text.Substring(1);
                    }
                    else
                    {
                        text = text.Substring(0, text.Length - 1);
                    }
                }
                if (alignRight)
                {
                    return dDot + new string(' ', lengthB - GetLenB(text)) + text;
                }
                else
                {
                    return text + new string(' ', lengthB - GetLenB(text)) + dDot;
                }
            }
        }

        /// <summary>
        /// 整理文本,把一段文本整理成指定长度并居中
        /// </summary>
        /// <param name="text"></param>
        /// <param name="lengthB"></param>
        /// <param name="manyIsDot"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string CenterText(string text, int lengthB, bool manyIsDot = false)
        {
            if (GetLenB(text) > lengthB)
            {
                if (manyIsDot)
                {
                    do
                    {
                        text = text.Substring(0, text.Length - 1);
                    }
                    while (GetLenB(text) + 2 > lengthB);
                    text = text + new string(' ', lengthB - GetLenB(text) - 2) + "…";
                }
                else
                {
                    do
                    {
                        text = text.Substring(0, text.Length - 1);
                    }
                    while (GetLenB(text) > lengthB);
                    text += new string(' ', lengthB - GetLenB(text));
                }
                return text;
            }
            else
            {
                text = new string(' ', (lengthB - GetLenB(text)) / 2) + text;
                return text + new string(' ', lengthB - GetLenB(text));
            }
        }

        [DllImport("winspool.drv", CharSet = CharSet.Ansi, EntryPoint = "OpenPrinterA", ExactSpelling = true, SetLastError = true)]
        private static extern bool OpenPrinter([MarshalAs(UnmanagedType.VBByRefStr)] ref string strPrinterName, ref IntPtr ptrPrinter, IntPtr ptrDefalut);

        [DllImport("winspool.drv", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        private static extern bool ClosePrinter(IntPtr ptrPrinter);

        [DllImport("winspool.drv", CharSet = CharSet.Ansi, EntryPoint = "AddJobA", ExactSpelling = true, SetLastError = true)]
        private static extern bool AddJob(IntPtr ptrPrinter, int iLevel, IntPtr ptrJob, int iSize, ref int iCpSize);

        [DllImport("winspool.drv", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        private static extern int ScheduleJob(IntPtr ptrPrinter, int JobID);

        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        private static extern int GetLastError();

        [DllImport("kernel32", CharSet = CharSet.Ansi, EntryPoint = "CreateFileA", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateFile([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpFileName, int dwDesiredAccess, int dwShareMode, int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);

        /// <summary>
        /// 向小票打印机打印内容,错误时返回错误说明,否则返回"".(自己生成单尾的空行,不应使用于开钱箱,否则开钱箱时会出纸)
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string DoPrint(string printerName, string text)
        {
            for (int xx = 1; xx < mMargenLines; xx++) text += "\r\n";
            return DoPrintM(printerName, text);
        }

        /// <summary>
        /// 向小票打印机打印内容,错误时返回错误说明,否则返回"".(自己不生成单尾的空行,可用于开钱箱)
        /// </summary>
        /// <param name="printerName"></param>
        /// <param name="text"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string DoPrintM(string printerName, string text)
        {
            if (printerName == null || printerName.Trim().Length == 0) return "打印失败,打印机名称不能为空";

            printerName = printerName.Trim();
            if (printerName.StartsWith("\\\\") || printerName.EndsWith(":"))
            {
                var dPrintHandle = CreateFile(ref printerName, -1073741824, 0, 0, 3, 0, 0);
                if (dPrintHandle.ToInt32() == -1)
                {
                    return "打印失败,不能连接到打印机";
                }
                try
                {
                    var dHandle = new SafeFileHandle(dPrintHandle, true);
                    var fs = new FileStream(dHandle, FileAccess.ReadWrite);
                    var sw = new StreamWriter(fs, Encoding.Default);
                    sw.Write(text);
                    sw.Close();
                    fs.Close();
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }
            else
            {
                var dJobHandle = Marshal.AllocHGlobal(120);
                var dPrintHandle = new IntPtr();
                if (!OpenPrinter(ref printerName, ref dPrintHandle, IntPtr.Zero))
                {
                    Marshal.FreeHGlobal(dJobHandle);
                    return "打印失败,不能连接到打印机";
                }
                var dSize = 0;
                AddJob(dPrintHandle, 1, dJobHandle, 120, ref dSize);
                var obj = Marshal.PtrToStructure(dJobHandle, typeof(ADDJOB_INFO_1));
                var dJobInfo = obj != null ? (ADDJOB_INFO_1)obj : new ADDJOB_INFO_1 { JobID = 0, lpPath = "" };
                File.WriteAllText(dJobInfo.lpPath, text, Encoding.Default);
                var dErr = ScheduleJob(dPrintHandle, dJobInfo.JobID);
                ClosePrinter(dPrintHandle);
                Marshal.FreeHGlobal(dJobHandle);
                if (dErr == 0) return "打印失败";
            }
            return "";
        }

        /// <summary>POS机一行能容纳的窄字符数量,共32个</summary>
        public const int LengthB = 32;

        /// <summary>POS打印机一行的总点数,共384点</summary>
        public const int LengthDot = 384;
        private static int mMargenLines = 0;

        /// <summary>
        /// 金额符号(￥)
        /// </summary>
        /// <remarks></remarks>
        public const string MoneyOper = "￥";

        //private const int GENERIC_READ = -2147483648;
        //private const int GENERIC_WRITE = 1073741824;
        //private const int OPEN_EXISTING = 3;

        private struct ADDJOB_INFO_1
        {
            public string lpPath;
            public int JobID;
        }
    }
}
